///
/// Copyright (c) 2019 Erwin Rol <erwin@erwinrol.com>
///
/// SPDX-License-Identifier: Apache-2.0
///

#ifndef ZPP_INCLUDE_ZPP_FMT_HPP
#define ZPP_INCLUDE_ZPP_FMT_HPP

#include <bitset>
#include <chrono>
#include <cstddef>
#include <string_view>
#include <utility>
#include <zephyr/kernel.h>
#include <zephyr/sys/__assert.h>
#ifdef CONFIG_PIGWEED_STATUS
#include "pw_status/status.h"
#endif
#include "error.hpp"
namespace zpp {
namespace internal {

enum class int_base { x, X, b, B, d, o, none };
inline void print_arg() noexcept {}
inline void print_arg(bool v) noexcept { printk("%s", v ? "true" : "false"); }
inline void print_arg(float v) noexcept { printk("%f", (double)v); }
inline void print_arg(double v) noexcept { printk("%g", v); }
inline void print_arg(char v) noexcept { printk("%c", v); }
inline void print_arg(const char *v) noexcept { printk("%s", v); }
inline void print_arg(const void *v) noexcept { printk("%p", v); }
inline void print_arg(uint8_t v) noexcept { printk("%d", (uint32_t)v); }
inline void print_arg(std::byte v) noexcept {
  printk("%d", static_cast<uint8_t>(v));
}

inline void print_arg(int8_t v) noexcept { printk("%d", (int32_t)v); }
inline void print_arg(uint16_t v) noexcept { printk("%d", (uint32_t)v); }
inline void print_arg(int16_t v) noexcept { printk("%d", (int32_t)v); }
inline void print_arg(uint32_t v) noexcept { printk("%d", v); }
inline void print_arg(int32_t v) noexcept { printk("%d", v); }
inline void print_arg(uint64_t v) noexcept { printk("%lld", v); }
inline void print_arg(int64_t v) noexcept { printk("%lld", v); }
inline void print_arg(error err) noexcept { printk("%s", err.str()); }
inline void print_arg(error_code err) noexcept { printk("%s", error_str(err)); }
inline void print_arg(auto v, int_base base)
  requires(std::disjunction_v<std::is_integral<decltype(v)>,
                              std::is_same<decltype(v), std::byte>>)
{
  auto print_binary = [](auto num) {
    if (num == 0) {
      printk("0\n");
      return;
    }

    int leading_zero = 1;
    for (int i = sizeof(num) * 8 - 1; i >= 0; i--) {
      int bit = (num >> i) & 1;

      if (bit == 1) {
        leading_zero = 0;
      }

      if (!leading_zero) {
        printk("%d", bit);
      }
    }
  };
  switch (base) {
  case zpp::internal::int_base::x:
    printk("%x", static_cast<uint32_t>(v));
    return;
  case zpp::internal::int_base::X:
    printk("%X", static_cast<uint32_t>(v));
    return;
  case zpp::internal::int_base::o:
    printk("%o", static_cast<uint32_t>(v));
    return;
  case zpp::internal::int_base::b:
    print_binary(static_cast<uint32_t>(v));
    return;
  case zpp::internal::int_base::B:
    print_binary(static_cast<uint32_t>(v));
    return;
  default:
    return;
  }
}
inline void print_arg(const std::string_view v) noexcept {
  for (auto ch : v) {
    printk("%c", ch);
  }
}

#ifdef CONFIG_PIGWEED_STATUS
inline void print_arg(pw::Status v) noexcept { printk("%s", v.str()); }
#endif

template <class T_Rep, class T_Period>
inline void print_arg(std::chrono::duration<T_Rep, T_Period> v) {
  using namespace std::chrono;

  auto s = duration_cast<seconds>(v);
  v -= duration_cast<decltype(v)>(s);
  auto ms = duration_cast<milliseconds>(v);
  v -= duration_cast<decltype(v)>(ms);
  auto us = duration_cast<microseconds>(v);
  v -= duration_cast<decltype(v)>(us);
  auto ns = duration_cast<nanoseconds>(v);

  printk("%d.%03d%03d%03ds", (int)s.count(), (int)ms.count(), (int)us.count(),
         (int)ns.count());
}

template <class T_Clock>
inline void print_arg(std::chrono::time_point<T_Clock> v) {
  print_arg(v.time_since_epoch());
}

inline void print_helper(const char *fmt) noexcept { printk("%s", fmt); }

template <class T_FirstArg, class... T_Args>
inline void print_helper(const char *fmt, T_FirstArg &&first,
                         T_Args &&...args) noexcept {
  enum class state { normal, format, open_brace, close_brace, base, done };
  state s = state::normal;
  int_base base = int_base::none;
  size_t n = 0;
  bool prefixed = false;
  while (true) {
    char c = fmt[n++];

    if (c == '\0') {
      return;
    }

    switch (s) {
    case state::normal:
      switch (c) {
      case '{':
        s = state::open_brace;
        break;
      case '}':
        s = state::close_brace;
        break;
      default:
        printk("%c", c);
        break;
      }
      break;

    case state::open_brace:
      switch (c) {
      case '{':
        s = state::normal;
        printk("{");
        break;

      case '}':
        s = state::done;
        break;

      case ':':
        s = state::base;
        break;

      default:
        s = state::format;
        break;
      }
      break;
    case state::base:
      if (c == '#') {
        prefixed = true;
        break;
      }
      s = state::open_brace;
      switch (c) {
      case 'X':
        if (prefixed) {
          printk("0X");
        }
        base = int_base::X;
        break;
      case 'B':
        if (prefixed) {
          printk("0B");
        }
        base = int_base::B;
        break;
      case 'x':
        if (prefixed) {
          printk("0x");
        }
        base = int_base::x;
        break;
      case 'b':
        if (prefixed) {
          printk("0b");
        }
        base = int_base::b;
        break;
      case 'd':
        base = int_base::d;
        break;
      case 'o':
        if (prefixed) {
          printk("0");
        }
        base = int_base::o;
        break;
      default:
        base = int_base::none;
        break;
      }
      break;
    case state::close_brace:
      if (c == '}') {
        printk("}");
      }
      s = state::normal;
      break;

    case state::format:
      switch (c) {
      case '}':
        s = state::done;
        break;
      default:
        break;
      }
      break;

    case state::done:
      break;
    }

    if (s == state::done) {
      break;
    }
  }
  if constexpr (std::is_integral_v<std::remove_cv_t<decltype(first)>> or
                std::is_enum_v<std::remove_cv_t<decltype(first)>>) {
    if (base == int_base::none) {
      print_arg(std::forward<T_FirstArg>(first));
    } else {
      print_arg(std::forward<T_FirstArg>(first), base);
    }
  } else {
    print_arg(std::forward<T_FirstArg>(first));
  }
  print_helper(&(fmt[n]), std::forward<T_Args>(args)...);
}

} // namespace internal

///
/// @brief simple typesafe print function
///
/// print uses the same format string syntax as the fmt C++ lib, just
/// that the feature set is very limited. It only supports {} without
/// any options, for example print("Nr: {}", 1);
///
/// @param fmt The format string using {} as place holder
/// @param args The needed arguments to print
///
template <class... T_Args>
inline void print(const char *fmt, T_Args &&...args) noexcept {
  internal::print_helper(fmt, std::forward<T_Args>(args)...);
}

template <class... T_Args>
inline void println(const char *fmt, T_Args &&...args) noexcept {
  internal::print_helper(fmt, std::forward<T_Args>(args)...);
  internal::print_helper("\n");
}

template <class T_Arg> inline void print(T_Arg &&arg) {
  print("{}", std::forward<T_Arg>(arg));
}
template <class T_Arg> inline void println(T_Arg &&arg) {
  print("{}\n", std::forward<T_Arg>(arg));
}

} // namespace zpp

#endif // ZPP_INCLUDE_ZPP_FMT_HPP
